// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/debug/debug-scope-iterator.h"

#include "src/api-inl.h"
#include "src/debug/debug.h"
#include "src/debug/liveedit.h"
#include "src/frames-inl.h"
#include "src/isolate.h"
#include "src/objects/js-generator-inl.h"
#include "src/wasm/wasm-objects-inl.h"

namespace v8 {

std::unique_ptr<debug::ScopeIterator> debug::ScopeIterator::CreateForFunction(
    v8::Isolate* v8_isolate, v8::Local<v8::Function> v8_func)
{
    internal::Handle<internal::JSReceiver> receiver = internal::Handle<internal::JSReceiver>::cast(Utils::OpenHandle(*v8_func));

    // Besides JSFunction and JSBoundFunction, {v8_func} could be an
    // ObjectTemplate with a CallAsFunctionHandler. We only handle plain
    // JSFunctions.
    if (!receiver->IsJSFunction())
        return nullptr;

    internal::Handle<internal::JSFunction> function = internal::Handle<internal::JSFunction>::cast(receiver);

    // Blink has function objects with callable map, JS_SPECIAL_API_OBJECT_TYPE
    // but without context on heap.
    if (!function->has_context())
        return nullptr;
    return std::unique_ptr<debug::ScopeIterator>(new internal::DebugScopeIterator(
        reinterpret_cast<internal::Isolate*>(v8_isolate), function));
}

std::unique_ptr<debug::ScopeIterator>
debug::ScopeIterator::CreateForGeneratorObject(
    v8::Isolate* v8_isolate, v8::Local<v8::Object> v8_generator)
{
    internal::Handle<internal::Object> generator = Utils::OpenHandle(*v8_generator);
    DCHECK(generator->IsJSGeneratorObject());
    return std::unique_ptr<debug::ScopeIterator>(new internal::DebugScopeIterator(
        reinterpret_cast<internal::Isolate*>(v8_isolate),
        internal::Handle<internal::JSGeneratorObject>::cast(generator)));
}

namespace internal {

    DebugScopeIterator::DebugScopeIterator(Isolate* isolate,
        FrameInspector* frame_inspector)
        : iterator_(isolate, frame_inspector)
    {
        if (!Done() && ShouldIgnore())
            Advance();
    }

    DebugScopeIterator::DebugScopeIterator(Isolate* isolate,
        Handle<JSFunction> function)
        : iterator_(isolate, function)
    {
        if (!Done() && ShouldIgnore())
            Advance();
    }

    DebugScopeIterator::DebugScopeIterator(Isolate* isolate,
        Handle<JSGeneratorObject> generator)
        : iterator_(isolate, generator)
    {
        if (!Done() && ShouldIgnore())
            Advance();
    }

    bool DebugScopeIterator::Done() { return iterator_.Done(); }

    void DebugScopeIterator::Advance()
    {
        DCHECK(!Done());
        iterator_.Next();
        while (!Done() && ShouldIgnore()) {
            iterator_.Next();
        }
    }

    bool DebugScopeIterator::ShouldIgnore()
    {
        if (GetType() == debug::ScopeIterator::ScopeTypeLocal)
            return false;
        return !iterator_.DeclaresLocals(i::ScopeIterator::Mode::ALL);
    }

    v8::debug::ScopeIterator::ScopeType DebugScopeIterator::GetType()
    {
        DCHECK(!Done());
        return static_cast<v8::debug::ScopeIterator::ScopeType>(iterator_.Type());
    }

    v8::Local<v8::Object> DebugScopeIterator::GetObject()
    {
        DCHECK(!Done());
        Handle<JSObject> value = iterator_.ScopeObject(i::ScopeIterator::Mode::ALL);
        return Utils::ToLocal(value);
    }

    int DebugScopeIterator::GetScriptId()
    {
        DCHECK(!Done());
        return iterator_.GetScript()->id();
    }

    v8::Local<v8::Value> DebugScopeIterator::GetFunctionDebugName()
    {
        DCHECK(!Done());
        Handle<Object> name = iterator_.GetFunctionDebugName();
        return Utils::ToLocal(name);
    }

    bool DebugScopeIterator::HasLocationInfo()
    {
        return iterator_.HasPositionInfo();
    }

    debug::Location DebugScopeIterator::GetStartLocation()
    {
        DCHECK(!Done());
        return ToApiHandle<v8::debug::Script>(iterator_.GetScript())
            ->GetSourceLocation(iterator_.start_position());
    }

    debug::Location DebugScopeIterator::GetEndLocation()
    {
        DCHECK(!Done());
        return ToApiHandle<v8::debug::Script>(iterator_.GetScript())
            ->GetSourceLocation(iterator_.end_position());
    }

    bool DebugScopeIterator::SetVariableValue(v8::Local<v8::String> name,
        v8::Local<v8::Value> value)
    {
        DCHECK(!Done());
        return iterator_.SetVariableValue(Utils::OpenHandle(*name),
            Utils::OpenHandle(*value));
    }

    DebugWasmScopeIterator::DebugWasmScopeIterator(Isolate* isolate,
        StandardFrame* frame,
        int inlined_frame_index)
        : isolate_(isolate)
        , frame_(frame)
        , inlined_frame_index_(inlined_frame_index)
        , type_(debug::ScopeIterator::ScopeTypeGlobal)
    {
    }

    bool DebugWasmScopeIterator::Done()
    {
        return type_ != debug::ScopeIterator::ScopeTypeGlobal && type_ != debug::ScopeIterator::ScopeTypeLocal;
    }

    void DebugWasmScopeIterator::Advance()
    {
        DCHECK(!Done());
        if (type_ == debug::ScopeIterator::ScopeTypeGlobal) {
            type_ = debug::ScopeIterator::ScopeTypeLocal;
        } else {
            // We use ScopeTypeWith type as marker for done.
            type_ = debug::ScopeIterator::ScopeTypeWith;
        }
    }

    v8::debug::ScopeIterator::ScopeType DebugWasmScopeIterator::GetType()
    {
        DCHECK(!Done());
        return type_;
    }

    v8::Local<v8::Object> DebugWasmScopeIterator::GetObject()
    {
        DCHECK(!Done());
        Handle<WasmDebugInfo> debug_info(
            WasmInterpreterEntryFrame::cast(frame_)->debug_info(), isolate_);
        switch (type_) {
        case debug::ScopeIterator::ScopeTypeGlobal:
            return Utils::ToLocal(WasmDebugInfo::GetGlobalScopeObject(
                debug_info, frame_->fp(), inlined_frame_index_));
        case debug::ScopeIterator::ScopeTypeLocal:
            return Utils::ToLocal(WasmDebugInfo::GetLocalScopeObject(
                debug_info, frame_->fp(), inlined_frame_index_));
        default:
            return v8::Local<v8::Object>();
        }
        return v8::Local<v8::Object>();
    }

    int DebugWasmScopeIterator::GetScriptId()
    {
        DCHECK(!Done());
        return -1;
    }

    v8::Local<v8::Value> DebugWasmScopeIterator::GetFunctionDebugName()
    {
        DCHECK(!Done());
        return Utils::ToLocal(isolate_->factory()->empty_string());
    }

    bool DebugWasmScopeIterator::HasLocationInfo() { return false; }

    debug::Location DebugWasmScopeIterator::GetStartLocation()
    {
        DCHECK(!Done());
        return debug::Location();
    }

    debug::Location DebugWasmScopeIterator::GetEndLocation()
    {
        DCHECK(!Done());
        return debug::Location();
    }

    bool DebugWasmScopeIterator::SetVariableValue(v8::Local<v8::String> name,
        v8::Local<v8::Value> value)
    {
        DCHECK(!Done());
        return false;
    }
} // namespace internal
} // namespace v8
